﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using NetMicro.Core.Helper;
using NetMicro.EventBus.Diagnostics;
using NetMicro.EventBus.Messages;

namespace NetMicro.EventBus.Internal
{
    public class MessageListener : IMessageListener
    {
        public MessageListener(
            IMessageSerializer messageSerializer,
            IEventHandlerFindProvider eventHandlerFindProvider,
            IEventHandlerInvoker eventHandlerInvoker,
            ILogger<MessageListener> logger, IOptions<EventBusOptions> options)
        {
            MessageSerializer = messageSerializer;
            EventHandlerFindProvider = eventHandlerFindProvider;
            Logger = logger;
            Options = options;
            EventHandlerInvoker = eventHandlerInvoker;
        }
        protected static readonly DiagnosticListener s_diagnosticListener =
       new DiagnosticListener(DiagnosticListenerNames.DiagnosticListenerName);
        private IMessageSerializer MessageSerializer { get; }
        private IEventHandlerFindProvider EventHandlerFindProvider { get; }

        private IEventHandlerInvoker EventHandlerInvoker { get; }
        private ILogger<MessageListener> Logger { get; }
        private IOptions<EventBusOptions> Options { get; }

        public async Task<MessageReceiveResult> OnReceiveAsync(MessageTransfer message, CancellationToken cancellationToken)
        {
            long? tracingTimestamp = null;
            try
            {
                tracingTimestamp = TracingBefore(message);
                var descriptor = EventHandlerFindProvider.GetByName(message.EventHandlerName);
                if (descriptor is null)
                {
                    Logger.LogError($"[EventBus] not found of event handler: {message.EventHandlerName}, but receive msg: {message.MsgBody}.");
                    return MessageReceiveResult.Fail($"not found of event handler: {message.EventHandlerName}, but receive msg: {message.MsgBody}");
                }

                var now = DateTime.Now;
                message.Items ??= new Dictionary<string, string>();
                var messageTransfer = new MessageTransfer
                {
                    MsgId = message.MsgId,
                    Version = message.Version ?? Options.Value.Version,
                    SendAt = message.SendAt,
                    ExcuteAt = now,
                    EventHandlerName = message.EventHandlerName,
                    EventName = message.EventName,
                    Items = message.Items,
                    MsgBody = message.MsgBody
                };

                // 执行事件消费
                await EventHandlerInvoker.InvokeAsync(messageTransfer, descriptor).ConfigureAwait(false);
                TracingAfter(tracingTimestamp, messageTransfer);
                return MessageReceiveResult.Success();
            }
            catch (Exception ex)
            {
                TracingError(tracingTimestamp, message, ex);
                Logger.LogError(ex, $"[EventBus] message listening occur error.");
                return MessageReceiveResult.Fail($"{ex.StackTrace}");
            }
        }

        #region tracing
        private long? TracingBefore(MessageTransfer message)
        {
            if (s_diagnosticListener.IsEnabled(DiagnosticListenerNames.BeforeSub))
            {
                var eventData = new TraceEventData()
                {
                    OperationTimestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
                    Operation = message.EventName,
                    Message = message
                };

                s_diagnosticListener.Write(DiagnosticListenerNames.BeforeSub, eventData);
                return eventData.OperationTimestamp;
            }

            return null;
        }

        private void TracingAfter(long? tracingTimestamp, MessageTransfer message)
        {
            if (tracingTimestamp != null && s_diagnosticListener.IsEnabled(DiagnosticListenerNames.AfterSub))
            {
                var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
                var eventData = new TraceEventData()
                {
                    OperationTimestamp = now,
                    Operation = message.EventName,
                    Message = message,
                    ElapsedTimeMs = now - tracingTimestamp.Value
                };

                s_diagnosticListener.Write(DiagnosticListenerNames.AfterSub, eventData);
            }
        }

        private void TracingError(long? tracingTimestamp, MessageTransfer message, Exception ex)
        {
            if (tracingTimestamp != null && s_diagnosticListener.IsEnabled(DiagnosticListenerNames.ErrorSub))
            {
                var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
                var eventData = new TraceEventData()
                {
                    OperationTimestamp = now,
                    Operation = message.EventName,
                    Message = message,
                    ElapsedTimeMs = now - tracingTimestamp.Value,
                    Exception = ex
                };

                s_diagnosticListener.Write(DiagnosticListenerNames.ErrorSub, eventData);
            }
        }

        #endregion
    }
}